﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the Microsoft Public License (Ms-PL).
// See accompanying file License.txt or copy at http://mews.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Mews.Net.Nntp.Cache
{
    using System;
    using System.Collections.Generic;
    using System.Data.SqlServerCe;
    using System.Globalization;
    using System.IO;

    /// <summary>See <see cref="IHost"/>.</summary>
    internal sealed class Host : IntIdObject<Host, IHost>, IHost
    {
        private readonly Database database;
        private readonly string name;
        private readonly int port;
        private string userName;
        private string emailAddress;
        private byte[] accountName;
        private byte[] password;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="IHost.Name"/>.</summary>
        public string Name
        {
            get { return this.name; }
        }

        /// <summary>See <see cref="IHost.Port"/>.</summary>
        public int Port
        {
            get { return this.port; }
        }

        /// <summary>See <see cref="IHost.UserName"/>.</summary>
        public string UserName
        {
            get
            {
                return this.userName;
            }

            set
            {
                this.database.HostUpdater.UpdateUserName(this.Id, value);
                this.userName = value;
            }
        }

        /// <summary>See <see cref="IHost.EmailAddress"/>.</summary>
        public string EmailAddress
        {
            get
            {
                return this.emailAddress;
            }

            set
            {
                this.database.HostUpdater.UpdateEmailAddress(this.Id, value);
                this.emailAddress = value;
            }
        }

        /// <summary>See <see cref="IHost.AccountName"/>.</summary>
        public string AccountName
        {
            get
            {
                return this.database.Decrypt(this.accountName);
            }

            set
            {
                byte[] encrypted = this.database.Encrypt(value);
                this.database.HostUpdater.UpdateAccountName(this.Id, encrypted);
                this.accountName = encrypted;
            }
        }

        /// <summary>See <see cref="IHost.Password"/>.</summary>
        public string Password
        {
            get
            {
                return this.database.Decrypt(this.password);
            }

            set
            {
                byte[] encrypted = this.database.Encrypt(value);
                this.database.HostUpdater.UpdatePassword(this.Id, encrypted);
                this.password = encrypted;
            }
        }

        /// <summary>See <see cref="IDeletablePersistentObject.Delete"/>.</summary>
        public void Delete()
        {
            this.database.AssertSendReceiveNotInProgress();

            // Even though we have ON DELETE CASCADE, this is necessary due to a cyclic relation in the InboundArticle
            // table.
            foreach (var group in this.GetGroups())
            {
                group.DeleteAllInboundArticles();
            }

            this.DeleteAllOutboundArticles();
            SqlCeHelper.Delete(this.Connection, HostNames.Table, this.Id);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="IHost.AddGroup"/>.</summary>
        IGroup IHost.AddGroup(
            string groupName,
            string groupDisplayName,
            int newInboundArticlesReceiveLimit,
            int cachedInboundArticlesLimit)
        {
            Group.AssertValid(newInboundArticlesReceiveLimit, cachedInboundArticlesLimit);
            return this.AddGroup(
                groupName, groupDisplayName, newInboundArticlesReceiveLimit, cachedInboundArticlesLimit);
        }

        /// <summary>See <see cref="IHost.GetGroups"/>.</summary>
        IEnumerable<IGroup> IHost.GetGroups()
        {
            foreach (var group in this.GetGroups())
            {
                yield return group;
            }
        }

        /// <summary>See <see cref="IHost.AddOutboundArticle"/>.</summary>
        IOutboundArticle IHost.AddOutboundArticle()
        {
            return this.AddOutboundArticle();
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        internal Host(Database database, SqlCeDataReader reader) : base((int)reader[HostNames.IdColumn])
        {
            this.database = database;

            this.name = (string)reader[HostNames.NameColumn];
            this.port = (int)reader[HostNames.PortColumn];
            this.userName = reader.GetNullableColumn<string>(HostNames.UserNameColumn);
            this.emailAddress = reader.GetNullableColumn<string>(HostNames.EmailAddressColumn);
            this.accountName = reader.GetNullableColumn<byte[]>(HostNames.AccountNameColumn);
            this.password = reader.GetNullableColumn<byte[]>(HostNames.PasswordColumn);
        }

        internal Database Database
        {
            get { return this.database; }
        }

        internal string DataDirectory
        {
            get
            {
                return this.database.DataDirectory;
            }
        }

        internal SqlCeConnection Connection
        {
            get { return this.database.Connection; }
        }

        internal Group AddGroup(
            string groupName,
            string groupDisplayName,
            int newInboundArticlesReceiveLimit,
            int cachedInboundArticlesLimit)
        {
            return SqlCeHelper.Insert<Group>(
                this.CreateGroup,
                this.Connection,
                GroupNames.Table,
                GroupNames.InsertableColumns,
                this.Id,
                groupName,
                groupDisplayName,
                newInboundArticlesReceiveLimit,
                cachedInboundArticlesLimit);
        }

        internal IEnumerable<Group> GetGroups()
        {
            return SqlCeHelper.Select<Group>(
                this.CreateGroup,
                this.Connection,
                GroupNames.Table,
                GroupNames.HostIdNameIndex,
                DbRangeOptions.Prefix,
                new object[] { this.Id, string.Empty },
                null);
        }

        internal OutboundArticle AddOutboundArticle()
        {
            return SqlCeHelper.Insert<OutboundArticle>(
                reader => new OutboundArticle(this, reader),
                this.Connection,
                OutboundArticleNames.Table,
                OutboundArticleNames.InsertableColumns,
                this.Id,
                SendStatus.Draft);
        }

        internal IEnumerable<OutboundArticle> GetOutboundArticles(SendStatus sendStatus)
        {
            return SqlCeHelper.Select<OutboundArticle>(
                reader => new OutboundArticle(this, reader),
                this.Connection,
                OutboundArticleNames.Table,
                OutboundArticleNames.HostIdIsDeletedSendStatusDateUtcIndex,
                DbRangeOptions.Prefix,
                new object[] { this.Id, false, sendStatus },
                null);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private Group CreateGroup(SqlCeDataReader reader)
        {
            return new Group(this, reader);
        }

        private void DeleteAllOutboundArticles()
        {
            using (var command = CommandHelper.Create(
                this.Connection,
                OutboundArticleNames.Table,
                OutboundArticleNames.HostIdIsDeletedSendStatusDateUtcIndex,
                DbRangeOptions.Prefix,
                new object[] { this.Id },
                null))
            using (var resultSet = command.ExecuteResultSet(ResultSetOptions.Updatable | ResultSetOptions.Scrollable))
            {
                while (resultSet.ReadFirst())
                {
                    File.Delete(OutboundArticle.GetDataFilePath(
                        this.DataDirectory, (int)resultSet[OutboundArticleNames.IdColumn]));
                    resultSet.Delete();
                }
            }
        }
    }
}
